iTesting软件测试知识分享

PageObject 模式浅谈

PageObject 模式是什么?它如何起到到减少冗余代码,提升测试效率的目的?

先想象下这个例子:
在自动化测试过程中,特别是基于UI的功能自动化,我们经常要进行查找元素,操作元素的操作,一般来说,代码是如下这个样子的:

1
2
self.driver.find_element_by_xpath("//option[@value='10']")

如果这个元素会被多个method用到,那么当元素变化时(例如XPATH变了), 我们就不得不更新每个引用到这个元素的代码,非常麻烦。

有没有好的办法解决这个问题呢? PageObject 应运而生

Page Objects

Within your web app’s UI there are areas that your tests interact with. A Page Object simply models these as objects within the test code. This reduces the amount of duplicated code and means that if the UI changes, the fix need only be applied in one place.

简单来说就是:

PageObject是一种程序设计模式,将面向过程转变为面向对象(页面对象),将测试对象及单个的测试步骤封装在每个Page对象中,以page为单位进行管理。可以使代码复用,降低维护成本,提高程序可读性和编写效率。
PageObject可以将页面定位和业务操作分开,分离测试对象(元素对象)和测试脚本(用例脚本),提高用例的可维护性。

记住,PageObject是一种程序设计模式,是一种思想,

PageObject 的好处是:

Creating reusable code that can be shared across multiple test cases
Reducing the amount of duplicated code
If the user interface changes, the fix needs changes in only one place

PageObject应该怎么使用呢?基于测试实践,我们一般这样应用:

  1. 把每个要测试的对象封装在一个page内, 这个page里包含这个对象可能的所有操作。
  2. 创建一个BasePage,这个BasePage包含所有待测page都能用到的公用方法,这个BasePage对应的类应该是个抽象类。
  3. 测试脚本自由引用page及page里的方法。

python里有个第三方库page-objects, 基于python实现了PageObject模式,并且封装了很多有用的方法。我们以它为例,举例如下(例子基于我的Github项目):

abstract_base_page.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import time
from abc import ABCMeta, abstractmethod
from DateTime import DateTime
from assertpy import assert_that
from page_objects import PageObject, PageElement
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver import ActionChains
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import settings
class AbstractBasePage(PageObject):
__metaclass__ = ABCMeta
WAIT_FOR_SCROLL_TIME = 2
def __init__(self, driver):
# Don't change the variable name w. it's used by PageObject
self.w = driver
self.driver = driver
assert_that(self.has_loaded()).is_true()
def has_loaded(self):
try:
return self.is_target_page()
except NoSuchElementException:
return False
# This function will be implement by sub class
@abstractmethod
def is_target_page(self):
pass
def is_element_displayed_on_page(self, ele, timeout=settings.TIME_OUT):
try:
WebDriverWait(self.driver, timeout).until(lambda driver: ele.is_displayed())
result = True
except Exception:
result = False
return result
#...其它的公用方法.....

解释如下:
1.abc 模块用来创建抽象类。
2.page_objects模块用来实现pageobject模式。
语法请自行google。

baidu.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import time
from page_objects import page_element
import settings
from settings.test_config import DEFAULT_SLEEP_TIME
from page.abstract_base_page import AbstractBasePage
class BAI_DU(AbstractBasePage):
SEARCH_DIALOG_ID = "kw"
SEARCH_ICON_ID = "su"
MEMBER_COUNT_XPATH = "//option[@value='10']"
SAVE_XPATH = ".//*[@id='save']"
csearch_input_dialog = page_element(id_=SEARCH_DIALOG_ID)
search_icon = page_element(id_=SEARCH_ICON_ID)
member_count = page_element(xpath=MEMBER_COUNT_XPATH)
save_button = page_element(xpath=SAVE_XPATH)
def __init__(self, browser):
self.browser = browser
AbstractBasePage.__init__(self, browser)
def is_target_page(self):
self.browser.get(settings.BAI_DU_HOME + "/")
return self.is_element_displayed_on_page(self.search_icon)
def search(self, search_string):
self.search_input_dialog.send_keys(search_string)
self.search_icon.click()
time.sleep(DEFAULT_SLEEP_TIME)
self.browser.close()

解释如下:
1.page_objects封装了Selenium的一些方法,我们不需要通过driver.find_element_by_xpath()等来查找元素,转而用
pageelement(locator)这样的方式来定位, page-objects支持的locator如下:

keyword Arg Description
id_ Element ID attribute
css CSS Selector
name Element name attribute
class_name Element class name
tag_name Element HTML tag name
link_text Anchor Element text content
partial_link_text Anchor Element partial text content
xpath XPath

2.每个子类,都应该实现抽象类里的抽象方法, 在本例中是is_target_page().

test.py(测试用例)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from ptest.assertion import assert_that, assert_equals
from ptest.decorator import TestClass, BeforeMethod, Test, AfterMethod
from ptest.plogger import preporter
from common.selenium_helper import SeleniumHelper
from page.baidu import BAI_DU
@TestClass(run_mode='parallel')
class TestBaidu:
@BeforeMethod(description="prepare test data")
def before(self):
preporter.info("Test is about to start")
self.browser = SeleniumHelper.open_browser("chrome")
@Test(tags =["regression", "smoke"])
def test_baidu_search(self):
"""Test search"""
baidu = BAI_DU(self.browser)
baidu.search("test")
assert_equals(1, 1)

测试脚本,可以任何组合各个page的任意方法。

由此可以见, 使用了page_objects 后, 测试对象, 测试方法, 测试脚本全部解耦, 测试对象的任何改变,只需要更改相应的page对应的类就好了。

最后附上:
Page_objects的pypi地址

Page_objects文档

🐶 您的支持将鼓励我继续创作 🐶
-------------评论, 吐槽, 学习交流,请关注微信公众号 iTesting-------------
请关注微信公众号 iTesting wechat
扫码关注,跟作者互动